package keepalive

import (
	"bytes"
	"context"
	"crypto/md5"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"gitee.com/mjun1833/buildnotification/config"
	model2 "gitee.com/mjun1833/buildnotification/keepalive/model"
	"gitee.com/mjun1833/buildnotification/model"
	log "github.com/sirupsen/logrus"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	jsoniter "github.com/json-iterator/go"
)

var (
	ctx        context.Context
	cancelFunc context.CancelFunc
	waitGroup  = &sync.WaitGroup{}
)

func init() {
	ctx, cancelFunc = context.WithCancel(context.Background())
	ReLoad()
}

func Cancel() {
	cancelFunc()
}

func ReLoad() {
	Cancel()
	ctx, cancelFunc = context.WithCancel(context.Background())
	upViids := make([]model2.VIIDUp, 0)
	err := config.GetInstance().UnmarshalKey("upVIID", &upViids)
	if err != nil {
		panic(err)
	}
	for _, viid := range upViids {
		viid := viid
		go Run(viid)
	}
}

func Run(upVIID model2.VIIDUp) {
	defer func() {
		log.Infoln(upVIID.Keepalive.DeviceID + "退出保活机制")
	}()
	waitGroup.Add(1)
	defer waitGroup.Done()
	log.Debugln("keepalive service")

	//检查开关是否打开了
	if !upVIID.Keepalive.IsOpen {
		log.Infoln(upVIID.Keepalive.DeviceID + "keepalive service will not start")
		atomic.StoreInt32(&model.IsRegister, 1)
		return
	}

	upVIID.Addr.RegisterAddr = "http://" + upVIID.Keepalive.Address + model.RegisterAddress
	_, err := url.Parse(upVIID.Addr.RegisterAddr)
	if err != nil {
		log.Error(err)
		log.Info(upVIID.Keepalive.DeviceID + " register addr is invalid")
		return
	}

	upVIID.Addr.KeepaliveAddr = "http://" + upVIID.Keepalive.Address + model.KeepaliveAddress
	_, err = url.Parse(upVIID.Addr.KeepaliveAddr)
	if err != nil {
		log.Error(err)
		log.Info(upVIID.Keepalive.DeviceID + " keepalive addr is invalid")
		return
	}

	upVIID.Addr.UnRegisterAddr = "http://" + upVIID.Keepalive.Address + model.UnRegisterAddress
	_, err = url.Parse(upVIID.Addr.UnRegisterAddr)
	if err != nil {
		log.Error(err)
		log.Info(upVIID.Keepalive.DeviceID + " unRegisterUrl addr is invalid")
		return
	}

	interval, err := time.ParseDuration(upVIID.Keepalive.Interval + "s")
	if err != nil {
		log.Info(upVIID.Keepalive.DeviceID + " interval is invalid")
		log.Error(err)
		return
	}

	//开始进行保活操作
	for {
		//先进行注册操作
		if err = Register(upVIID); err != nil {
			log.Errorf("deviceId: %v,注册失败,err: %v", upVIID.Keepalive.DeviceID, err)
			time.Sleep(interval)
			break
		}
		//之后轮询进行保活
		for {
			select {
			case <-time.After(interval):
				if err := KeepAlive(upVIID); err != nil {
					log.Errorf("deviceId: %v,保活失败,err: %v", upVIID.Keepalive.DeviceID, err)
					break
				}
			}
		}
	}
}

func Register(viid model2.VIIDUp) error {
	atomic.StoreInt32(&model.IsRegister, 0)
	log.Infoln("开始向上级注册")
	request := model.RegisterUpgrade{}
	request.RegisterObject.DeviceID = viid.Keepalive.DeviceID
	reqBody, err := jsoniter.Marshal(request)
	if err != nil {
		log.Error(err)
		return err
	}

	log.Debugf("第一次请求尝试注册发送的请求body: %v", string(reqBody))
	//先进行第一次请求
	req, err := http.NewRequest(http.MethodPost, viid.Addr.RegisterAddr, bytes.NewReader(reqBody))
	if err != nil {
		log.Error(err)
		return err
	}
	req.Header["Content-Type"] = []string{model2.DefaultContentType}
	req.Header["User-Identify"] = []string{viid.LoginInfo.UserIdentify}
	req.Header["User-Name"] = []string{viid.LoginInfo.UserName}
	req.Header["User-IdNumber"] = []string{viid.LoginInfo.UserIDNumber}
	req.Header["Client-IP"] = []string{viid.LoginInfo.LocalIp}
	req.Header["OrgCode"] = []string{viid.LoginInfo.OrgCode}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	log.Debugf("第一次请求注册发送的返回http code: %v", resp.StatusCode)
	if http.StatusUnauthorized != resp.StatusCode && http.StatusOK != resp.StatusCode {
		respBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return fmt.Errorf("状态码错误, code:%v,resp: %v", resp.StatusCode, string(respBody))
	}

	//获得新header里面需要的内容
	digestParams := digestParams(resp)
	resp.Body.Close()
	digestParams["uri"] = model.RegisterAddress
	digestParams["method"] = http.MethodPost
	digestParams["username"] = viid.Keepalive.Digest.UserName
	digestParams["password"] = viid.Keepalive.Digest.Password
	digest := getDigestAuthrization(digestParams)

	//准备第二次的请求体
	log.Debugln("发出的第二次注册请求:" + string(reqBody))
	req, err = http.NewRequest(http.MethodPost, viid.Addr.RegisterAddr, bytes.NewReader(reqBody))
	if err != nil {
		log.Error(err)
		return err
	}

	req.Header["Content-Type"] = []string{model2.DefaultContentType}
	req.Header["User-Identify"] = []string{viid.LoginInfo.UserIdentify}
	req.Header["User-Name"] = []string{viid.LoginInfo.UserName}
	req.Header["User-IdNumber"] = []string{viid.LoginInfo.UserIDNumber}
	req.Header["Client-IP"] = []string{viid.LoginInfo.LocalIp}
	req.Header["OrgCode"] = []string{viid.LoginInfo.OrgCode}
	req.Header["Authorization"] = []string{digest}

	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		log.Error(err)
		return err
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error(err)
		return err
	}

	log.Infof("第二次请求注册发送的返回http code: %v", resp.StatusCode)
	if http.StatusOK != resp.StatusCode && http.StatusCreated != resp.StatusCode && http.StatusNoContent != resp.StatusCode {
		return fmt.Errorf("状态码错误, code:%v,resp: %v", resp.StatusCode, string(respBody))
	}

	log.Infof("返回的注册结果:" + string(respBody))
	response := model.ResponseStatus{}
	err = jsoniter.Unmarshal(respBody, &response)
	if err != nil {
		log.Error(err)
		return err
	}

	atomic.StoreInt32(&model.IsRegister, 1)
	return nil
}

func KeepAlive(viid model2.VIIDUp) error {
	request := model.KeepaliveUpgrade{}
	request.KeepaliveObject.DeviceID = viid.Keepalive.DeviceID
	reqBody, err := jsoniter.Marshal(request)
	if err != nil {
		log.Error(err)
		return err
	}
	log.Debugln("保活请求地址:", viid.Addr.KeepaliveAddr)
	log.Infof("发出的保活请求:" + string(reqBody))
	req, err := http.NewRequest(http.MethodPost, viid.Addr.KeepaliveAddr, bytes.NewReader(reqBody))
	if err != nil {
		log.Error(err)
		return err
	}

	req.Header["Content-Type"] = []string{model2.DefaultContentType}
	req.Header["User-Identify"] = []string{viid.LoginInfo.UserIdentify}
	req.Header["User-Name"] = []string{viid.LoginInfo.UserName}
	req.Header["User-IdNumber"] = []string{viid.LoginInfo.UserIDNumber}
	req.Header["Client-IP"] = []string{viid.LoginInfo.LocalIp}
	req.Header["OrgCode"] = []string{viid.LoginInfo.OrgCode}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Error(err)
		return err
	}
	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error(err)
		return err
	}
	log.Infof("返回的保活结果: %v,code: %v", string(respBody), resp.StatusCode)

	if http.StatusOK != resp.StatusCode && http.StatusCreated != resp.StatusCode && http.StatusNoContent != resp.StatusCode {
		log.Errorf("保活失败,重新注册,code: %v,resp: %v", resp.StatusCode, string(respBody))
		err = Register(viid)
		if err != nil {
			return err
		}
	}

	response := model.ResponseStatusSingle{}
	err = jsoniter.Unmarshal(respBody, &response)
	if err != nil {
		log.Error(err)
		return err
	}
	return nil
}

func digestParams(resp *http.Response) map[string]string {
	result := map[string]string{}
	if len(resp.Header["Www-Authenticate"]) > 0 {
		wantedHeaders := []string{"nonce", "realm", "qop"}
		responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
		for _, r := range responseHeaders {
			for _, w := range wantedHeaders {
				if strings.Contains(r, w) {
					result[w] = strings.Split(r, `"`)[1]
				}
			}
		}
	}
	return result
}

func getMD5(text string) string {
	hasher := md5.New()
	_, _ = hasher.Write([]byte(text))
	return hex.EncodeToString(hasher.Sum(nil))
}

func getCnonce() string {
	b := make([]byte, 8)
	_, err := io.ReadFull(rand.Reader, b)
	if err != nil {
		return ""
	}
	return fmt.Sprintf("%x", b)[:16]
}

func getDigestAuthrization(digestParts map[string]string) string {
	d := digestParts
	ha1 := getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
	ha2 := getMD5(d["method"] + ":" + d["uri"])
	nonceCount := 00000001
	if v, ok := d["nc"]; ok {
		nc, _ := strconv.Atoi(v)
		nonceCount = nc
		nonceCount++
	}
	cnonce := getCnonce()
	if v, ok := d["cnonce"]; ok {
		cnonce = v
	}
	response := getMD5(fmt.Sprintf("%s:%s:%v:%s:%s:%s", ha1, d["nonce"], nonceCount, cnonce, d["qop"], ha2))
	authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc="%v", qop="%s", response="%s"`,
		d["username"], d["realm"], d["nonce"], d["uri"], cnonce, nonceCount, d["qop"], response)
	return authorization
}
